package com.ziytek.web.citizen.configuration.db;

import com.ziytek.web.citizen.config.HistoryDbNameConfig;
import com.ziytek.web.citizen.constant.DdsConstant;
import com.ziytek.web.citizen.constant.NumConstant;
import com.ziytek.web.citizen.constant.ServerConstant;
import com.ziytek.web.citizen.service.AppDataService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.sqlite.SQLiteConfig;
import org.sqlite.SQLiteDataSource;
import org.sqlite.SQLiteOpenMode;

import javax.sql.DataSource;
import java.io.File;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * @author fengh
 * @date 2023/6/8 19:51
 */
@Slf4j
@Configuration
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
public class DynamicDatasourceConfig {


    private final AppDataService appDataService;
    private final HistoryDbNameConfig historyDbNameConfig;

    public DynamicDatasourceConfig(AppDataService appDataService, HistoryDbNameConfig historyDbNameConfig) {
        this.appDataService = appDataService;
        this.historyDbNameConfig = historyDbNameConfig;
    }

    private String getDbName(boolean basic) {
        //var userDirPath = LocationUtils.getLocation();
        var userDirPath = appDataService.getAppDataDir();
//        if (!userDirPath.contains(ServerConstant.APP_DATA_JT_CABINET) ||
//                !userDirPath.contains(ServerConstant.APP_DATA_DATA) ||
//                !userDirPath.contains(ServerConstant.APP_DATA_DB)) {
//            userDirPath = LocationUtils.getLocation();
//        }
        if (basic) {
            //return "jdbc:sqlite:" + LocationUtils.getLocation() + "\\db\\sqlite-basic.db?journal_mode=WAL";
            return "jdbc:sqlite:" + userDirPath + "\\db\\sqlite-basic.db?journal_mode=WAL";
        }

        var path = userDirPath + "\\db\\";
        log.info("path location:{}", path);

        // 文件夹是否存在，不存在创建
        var folder = new File(path);
        if (!folder.exists()) {
            log.info("DB folder does not exist, create folder:{}", path);
            folder.mkdir();
        }

        var day = DateFormatUtils.format(new Date(), ServerConstant.DATE_FORMAT_DAY_TWO);
        path = path + day.substring(0, 6);

        // 文件夹是否存在，不存在创建
        folder = new File(path);
        if (!folder.exists()) {
            log.info("SqliteDbName date DB folder does not exist, create folder:{}", path);
            folder.mkdir();
        }

        return "jdbc:sqlite:" + path + "\\"
                + ServerConstant.DB_START_WITH + day.substring(2)
                + ".db?journal_mode=WAL";
    }

    private String getHistoryDbName() {
        //var userDirPath = LocationUtils.getLocation();
        var userDirPath = appDataService.getAppDataDir();
//        if (!userDirPath.contains(ServerConstant.APP_DATA_JT_CABINET) ||
//                !userDirPath.contains(ServerConstant.APP_DATA_DATA) ||
//                !userDirPath.contains(ServerConstant.APP_DATA_DB)) {
//            userDirPath = LocationUtils.getLocation();
//        }
        var path = userDirPath + "\\db\\";
        log.info("HistoryDbName path location:{}", path);

        // 文件夹是否存在，不存在创建
        var folder = new File(path);
        if (!folder.exists()) {
            log.info("HistoryDbName DB folder does not exist, create folder:{}", path);
            folder.mkdir();
        }

        var day = DateFormatUtils.format(new Date(), ServerConstant.DATE_FORMAT_DAY_TWO);
        //path = path + day.substring(0, 6);

        // 文件夹是否存在，不存在创建
        folder = new File(path);
        if (!folder.exists()) {
            log.info("HistoryDbName SqliteDbName date DB folder does not exist, create folder:{}", path);
            folder.mkdir();
        }

        var name = historyDbNameConfig != null && historyDbNameConfig.nameNotNull() ?
                historyDbNameConfig.getHistoryDbName() : day.substring(NumConstant.INT_TWO);
        var strings = name.split(ServerConstant.DB_START_WITH);
        if (strings.length == NumConstant.INT_TWO) {
            path = path + "20" + strings[NumConstant.INT_ONE].substring(NumConstant.INT_ZERO, NumConstant.INT_FOUR);
        }

        return "jdbc:sqlite:" + path + "\\" + name + ".db?journal_mode=WAL";
    }

    @Primary
    @RefreshScope
    @Bean(name = DdsConstant.DS_MASTER_0)
    public DataSource mySqlDataSourcedataSource() {
        log.info("DynamicDatasourceConfig.dataSource() start");
        Map<Object, Object> dataSourceMap = new HashMap<>(16);
        dataSourceMap.put(DdsConstant.DS_MASTER, masterDataSource());
        dataSourceMap.put(DdsConstant.DS_BASIC, basicDataSource());
        dataSourceMap.put(DdsConstant.DS_HISTORY, historyDataSource());

        var dynamicDatasource = new DynamicDatasource();
        dynamicDatasource.setTargetDataSources(dataSourceMap);
        dynamicDatasource.setDefaultTargetDataSource(masterDataSource());
        log.info("DynamicDatasourceConfig.dataSource() end");
        return dynamicDatasource;
    }

    /**
     * 业务主数据库
     * 主要用于数据库数据写入
     *
     * @return DataSource
     */
    @RefreshScope
    @Order(NumConstant.INT_TWO)
    @Bean(DdsConstant.DS_MASTER)
    public DataSource masterDataSource() {
        var url = getDbName(false);

        log.info("masterDataSource Loading DB file:{}", url);
        var config = new SQLiteConfig();
        //指定数据库连接可以读写。
        config.setOpenMode(SQLiteOpenMode.READWRITE);
        //如果数据库不存在，则创建。
        config.setOpenMode(SQLiteOpenMode.CREATE);
        config.setSharedCache(true);
        config.setExplicitReadOnly(true);
        /*
         *config.resetOpenMode(SQLiteOpenMode.READWRITE);
         *config.resetOpenMode(SQLiteOpenMode.CREATE);
         * 设置数据库连接运行在多线程模式(没有指定单线程模式的情况下)
         * 禁用数据库连接和prepared statement(准备好的语句)上的锁
         * 因此不能在多个线程中并发使用同一个数据库连接
         *
         *config.setOpenMode(SQLiteOpenMode.NOMUTEX);
         *
         *页大小默认是 1024Byte(1K)，缓存大小默认是 1000 页
         */
        config.setDefaultCacheSize(5000);

        /*
         * 在max ios支持，
         * 可以严格保证写入顺序跟提交顺序一致
         * 设备开发商为了测评数据好看，往往会对提交的数据进行重排，再统一写入，
         * 亦即写入顺序跟App提交的顺序不一致。在某些情况下，例如断电，就可能导致写入文件不一致的情况，导致文件损坏。
         */
        config.enableFullSync(true);
        /*
         * 串行模式支持多线程操作，但是必须*统一使用一个全局的数据库连接*.
         * 这一点非常重要。串行模式会打开sqlite3所有的锁，在同一时刻保证只有一个线程能访问。
         * 这里可以理解为只有一条指向数据库的连接，多个线程的请求将会在该连接上串行传输。
         */
        config.setOpenMode(SQLiteOpenMode.FULLMUTEX);
        config.setBusyTimeout(1000);
        config.setBusyTimeout(15000);
        /*
         * 当synchronous设置为FULL (2), SQLite 数据库引擎在紧急时刻会暂停以确定数据已经写入磁盘。这使 系统崩溃或电源出问题时能确保数据库在重起后不会损坏。FULL synchronous很安全但很慢。
         *
         * 当synchronous设置为NORMAL, SQLite数据库引擎在大部分紧急时刻会暂停，但不像FULL模式下那么频繁。 NORMAL模式下有很小的几率(但不是不存在)发生电源故障导致数据库损坏的情况。但实际上，在这种情况 下很可能你的硬盘已经不能使用，或者发生了其他的不可恢复的硬件错误。
         *
         * 设置为synchronous OFF (0)时，SQLite在传递数据给系统以后直接继续而不暂停。若运行SQLite的应用程序崩溃， 数据不会损伤，但在系统崩溃或写入数据时意外断电的情况下数据库可能会损坏。另一方面，在synchronous OFF时 一些操作可能会快50倍甚至更多。在SQLite 2中，缺省值为NORMAL.而在3中修改为FULL。
         */
        var property = System.getProperty("db.sqlite.UnSynchronous");
        if (ServerConstant.TRUE.equalsIgnoreCase(property)) {
            log.info("masterDataSource Synchronous=OFF");
            log.info("masterDataSource SQLite在传递数据给系统以后直接继续而不暂停。若运行SQLite的应用程序崩溃， 数据不会损伤，但在系统崩溃或写入数据时意外断电的情况下数据库可能会损坏。另一方面，在synchronous OFF时 一些操作可能会快50倍甚至更多。");
            config.setSynchronous(SQLiteConfig.SynchronousMode.OFF);
        } else {
            config.setSynchronous(SQLiteConfig.SynchronousMode.FULL);
        }

        //config.enforceThreadSafety();
        config.enforceForeignKeys(true);
        config.setSynchronous(SQLiteConfig.SynchronousMode.OFF);
        config.setJournalMode(SQLiteConfig.JournalMode.WAL);
        config.setSharedCache(true);
        var dataSource = new SQLiteDataSource(config);
        log.info("masterDataSource Database connection address:{}", url);
        dataSource.setUrl(url);
        return dataSource;
    }

    /**
     * 基础表数据库
     * 基础表数据
     *
     * @return DataSource
     */
    @RefreshScope
    @Order(NumConstant.INT_ONE)
    @Bean(DdsConstant.DS_BASIC)
    public DataSource basicDataSource() {
        var url = getDbName(true);

        log.info("basicDataSource Loading DB file:{}", url);
        var config = new SQLiteConfig();
        //指定数据库连接可以读写。
        config.setOpenMode(SQLiteOpenMode.READWRITE);
        //如果数据库不存在，则创建。
        config.setOpenMode(SQLiteOpenMode.CREATE);
        config.setSharedCache(true);
        config.setExplicitReadOnly(true);
        //config.resetOpenMode(SQLiteOpenMode.READWRITE);
        //config.resetOpenMode(SQLiteOpenMode.CREATE);
        /*
         * 设置数据库连接运行在多线程模式(没有指定单线程模式的情况下)
         * 禁用数据库连接和prepared statement(准备好的语句)上的锁
         * 因此不能在多个线程中并发使用同一个数据库连接
         *
         * config.setOpenMode(SQLiteOpenMode.NOMUTEX);

         *页大小默认是 1024Byte(1K)，缓存大小默认是 1000 页
         */
        config.setDefaultCacheSize(5000);

        /*
         * 在max ios支持，
         * 可以严格保证写入顺序跟提交顺序一致
         * 设备开发商为了测评数据好看，往往会对提交的数据进行重排，再统一写入，
         * 亦即写入顺序跟App提交的顺序不一致。在某些情况下，例如断电，就可能导致写入文件不一致的情况，导致文件损坏。
         */
        config.enableFullSync(true);
        /*
         * 串行模式支持多线程操作，但是必须*统一使用一个全局的数据库连接*.
         * 这一点非常重要。串行模式会打开sqlite3所有的锁，在同一时刻保证只有一个线程能访问。
         * 这里可以理解为只有一条指向数据库的连接，多个线程的请求将会在该连接上串行传输。
         */
        config.setOpenMode(SQLiteOpenMode.FULLMUTEX);
        config.setBusyTimeout(1000);
        config.setBusyTimeout(15000);
        /*
         * 当synchronous设置为FULL (2), SQLite 数据库引擎在紧急时刻会暂停以确定数据已经写入磁盘。这使 系统崩溃或电源出问题时能确保数据库在重起后不会损坏。FULL synchronous很安全但很慢。
         *
         * 当synchronous设置为NORMAL, SQLite数据库引擎在大部分紧急时刻会暂停，但不像FULL模式下那么频繁。 NORMAL模式下有很小的几率(但不是不存在)发生电源故障导致数据库损坏的情况。但实际上，在这种情况 下很可能你的硬盘已经不能使用，或者发生了其他的不可恢复的硬件错误。
         *
         * 设置为synchronous OFF (0)时，SQLite在传递数据给系统以后直接继续而不暂停。若运行SQLite的应用程序崩溃， 数据不会损伤，但在系统崩溃或写入数据时意外断电的情况下数据库可能会损坏。另一方面，在synchronous OFF时 一些操作可能会快50倍甚至更多。在SQLite 2中，缺省值为NORMAL.而在3中修改为FULL。
         */
        var property = System.getProperty("db.sqlite.UnSynchronous");
        if (ServerConstant.TRUE.equalsIgnoreCase(property)) {
            log.info("basicDataSource Synchronous=OFF");
            log.info("basicDataSource SQLite在传递数据给系统以后直接继续而不暂停。若运行SQLite的应用程序崩溃， 数据不会损伤，但在系统崩溃或写入数据时意外断电的情况下数据库可能会损坏。另一方面，在synchronous OFF时 一些操作可能会快50倍甚至更多。");
            config.setSynchronous(SQLiteConfig.SynchronousMode.OFF);
        } else {
            config.setSynchronous(SQLiteConfig.SynchronousMode.FULL);
        }

        config.setSynchronous(SQLiteConfig.SynchronousMode.OFF);
        config.setJournalMode(SQLiteConfig.JournalMode.WAL);
        config.setSharedCache(true);
        var dataSource = new SQLiteDataSource(config);
        log.info("basicDataSource database connection address:{}", url);
        dataSource.setUrl(url);
        return dataSource;
    }

    /**
     * 历史表数据库
     * 历史表数据
     *
     * @return DataSource
     */
    @RefreshScope
    @Order(NumConstant.INT_TWO)
    @Bean(DdsConstant.DS_HISTORY)
    public DataSource historyDataSource() {
        var url = getHistoryDbName();

        log.info("historyDataSource Loading DB file:{}", url);
        var config = new SQLiteConfig();
        config.setBusyTimeout(1000);
        config.setBusyTimeout(15000);
        config.setSharedCache(true);
        config.enableFullSync(true);
        config.setExplicitReadOnly(true);
        config.setDefaultCacheSize(5000);
        config.setOpenMode(SQLiteOpenMode.CREATE);
        config.setOpenMode(SQLiteOpenMode.FULLMUTEX);
        config.setOpenMode(SQLiteOpenMode.READWRITE);

        var property = System.getProperty("db.sqlite.UnSynchronous");
        if (ServerConstant.TRUE.equalsIgnoreCase(property)) {
            log.info("historyDataSource Synchronous=OFF");
            log.info("historyDataSource SQLite在传递数据给系统以后直接继续而不暂停。若运行SQLite的应用程序崩溃， 数据不会损伤，但在系统崩溃或写入数据时意外断电的情况下数据库可能会损坏。另一方面，在synchronous OFF时 一些操作可能会快50倍甚至更多。");
            config.setSynchronous(SQLiteConfig.SynchronousMode.OFF);
        } else {
            config.setSynchronous(SQLiteConfig.SynchronousMode.FULL);
        }

        config.setSharedCache(true);
        config.setJournalMode(SQLiteConfig.JournalMode.WAL);
        config.setSynchronous(SQLiteConfig.SynchronousMode.OFF);
        var dataSource = new SQLiteDataSource(config);
        log.info("historyDataSource database connection address:{}", url);
        dataSource.setUrl(url);
        return dataSource;
    }
}
